﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace GameEngine
{
    /// <summary>
    /// Interface for object supporting GameEngine library with localized texts
    /// </summary>
    public interface ILocalizer
    {
        string GetStringByKey(string sKey);
        string FormatMessage(string sKey, params object[] parameters);
    }

    /// <summary>
    /// Defines types of games supported
    /// </summary>
    public enum enumGameType { Czech, English, International, Italian, Russian, SouthGerman, Spanish }

    /// <summary>
    /// Defines the type of figure used
    /// </summary>
    public enum enumFigureType { Nothing = 0, WhiteMan = 1, BlackMan = 2, WhiteKing = 3, BlackKing = 4 }

    /// <summary>
    /// Defines the side/color of the and player
    /// </summary>
    public enum enumFigureColor { White, Black }

    /// <summary>
    /// History model type
    /// </summary>
    public enum enumHistoryModel { Linear, Tree }

    /// <summary>
    /// Extension class for enumFigureColor for getting opposite color
    /// </summary>
    public static class enumFigureColorExtension
    {
        public static enumFigureColor Opposite(this enumFigureColor color)
        {
            return color == enumFigureColor.White ? enumFigureColor.Black : enumFigureColor.White;
        }
    }

    /// <summary>
    /// Extension class with helper methods for enumFigureType - type of figure/piece
    /// </summary>
    public static class enumFigureTypeExtension
    {
        public static enumFigureColor FigureColor(this enumFigureType figureType)
        {
            Debug.Assert(figureType != enumFigureType.Nothing);
            return (figureType == enumFigureType.WhiteMan || figureType == enumFigureType.WhiteKing)
                ? enumFigureColor.White : enumFigureColor.Black;
        }

        public static bool IsKing(this enumFigureType figureType)
        {
            Debug.Assert(figureType != enumFigureType.Nothing);
            return (figureType == enumFigureType.WhiteKing || figureType == enumFigureType.BlackKing)
                ? true : false;
        }
    }

    /// <summary>
    /// Bitboard - way of compressing game history to this class
    /// </summary>
    public struct BitBoard
    {
        public ulong bitWM;
        public ulong bitWQ;
        public ulong bitBM;
        public ulong bitBQ;
        public enumFigureColor onMove;
    }

    
    /// <summary>
    /// Direction, in which figures can move
    /// </summary>
    public enum enumDiagonalDir { NorthWest, NorthEast, SouthWest, SouthEast }

    /// <summary>
    /// enum class for game result
    /// </summary>
    public enum enumGameResult { WhiteWin, BlackWin, Draw, Unknown }

    /// <summary>
    /// Helper extension class for direction of move
    /// </summary>
    public static class enumDiagonalDirExtension
    {
        public static enumDiagonalDir Opposite(this enumDiagonalDir dir)
        {
            switch(dir)
            {
                case enumDiagonalDir.NorthEast:
                    return enumDiagonalDir.SouthWest;
                case enumDiagonalDir.NorthWest:
                    return enumDiagonalDir.SouthEast;
                case enumDiagonalDir.SouthEast:
                    return enumDiagonalDir.NorthWest;
                case enumDiagonalDir.SouthWest:
                    return enumDiagonalDir.NorthEast;
                default:
                    throw new InvalidOperationException("Invalid direction.");
            }
        }
    }

    
    
    /// <summary>
    /// Helper structure for working with position at the board
    /// </summary>
    public struct Position
    {
        public int Row;
        public int Col;


        public Position(int iRow, int iCol)
        { Row = iRow; Col = iCol; }

        /// <summary>
        /// Checks if it is empty position
        /// </summary>
        public bool IsEmpty()
        {
            return Row == -1 || Col == -1;
        }

        /// <summary>
        /// Property to get empty position
        /// </summary>
        public static Position Empty
        {
            get
            {
                return new Position(-1, -1);
            }
        }

        /// <summary>
        /// properties for getting new direction of the field
        /// </summary>
        public Position NorthWest
        {
            get { return new Position(Row + 1, Col - 1); }
        }

        public Position NorthEast
        {
            get { return new Position(Row + 1, Col + 1); }
        }

        public Position SouthWest
        {
            get { return new Position(Row - 1, Col - 1); }
        }

        public Position SouthEast
        {
            get { return new Position(Row - 1, Col + 1); }
        }

        /// <summary>
        /// Checks if the position fields are on the diagonal
        /// </summary>
        public bool IsDiagonal(Position pos)
        {
            return Math.Abs(this.Row - pos.Row) == Math.Abs(this.Col - pos.Col);
        }

        /// <summary>
        /// gets the direction at which player should go from THIS field to parameter field
        /// </summary>
        public enumDiagonalDir GetDiagonalDir(Position end)
        {
            Debug.Assert(IsDiagonal(end) == true);
            Debug.Assert( this != end );
            if (this.Row < end.Row)
            {
                if (this.Col < end.Col)
                    return enumDiagonalDir.NorthEast;
                else return enumDiagonalDir.NorthWest;
            }
            else
            {
                if (this.Col < end.Col)
                    return enumDiagonalDir.SouthEast;
                else return enumDiagonalDir.SouthWest;
            }
        }

        /// <summary>
        /// Gets next field according to the enumDiagonalDir enum class
        /// </summary>
        public Position GetNextField(enumDiagonalDir dir)
        {
            switch (dir)
            {
                case enumDiagonalDir.NorthWest:
                    return this.NorthWest;
                case enumDiagonalDir.NorthEast:
                    return this.NorthEast;
                case enumDiagonalDir.SouthWest:
                    return this.SouthWest;
                case enumDiagonalDir.SouthEast:
                    return this.SouthEast;
                default:
                    throw new InvalidOperationException("Unknown diagonal.");
            }
        }

        public static bool operator ==(Position pos1, Position pos2)
        {
            return pos1.Row == pos2.Row && pos1.Col == pos2.Col;
        }

        public static bool operator !=(Position pos1, Position pos2)
        {
            return (pos1.Row == pos2.Row && pos1.Col == pos2.Col) ? false : true;
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        /// returns letter of given column by index for chess coordinates
        /// </summary>
        private char GetColumnChar(int column)
        {
            switch (column)
            {
                case 0:
                    return 'a';
                case 1:
                    return 'b';
                case 2:
                    return 'c';
                case 3:
                    return 'd';
                case 4:
                    return 'e';
                case 5:
                    return 'f';
                case 6:
                    return 'g';
                case 7:
                default:
                    return 'h';
            }
        }

        /// <summary>
        /// Converts given position/field to Chess coordinates
        /// </summary>
        public override string ToString()
        {
            return GetColumnChar(Col) + (Row + 1).ToString();
        }
    }

    
    /// <summary>
    /// General container class for moves
    /// </summary>
    public class Move: GameItem
    {
        public Position startPos = new Position(0,0);
        public Position endPos = new Position(0, 0);
        public bool RemovingFigure = false;
        public enumFigureType removedFigure = enumFigureType.Nothing;
        public Position removedFigurePlace = new Position(0, 0);
        public bool convertedToKing = false;
        public Move nextMove = null;
        public BitBoard bitOriginalPosition;
        public string MoveStrength;

        public Move Copy()
        {
            Move m = new Move();
            m.startPos = this.startPos;
            m.endPos = this.endPos;
            m.RemovingFigure = this.RemovingFigure;
            m.removedFigure = this.removedFigure;
            m.removedFigurePlace = this.removedFigurePlace;
            m.convertedToKing = this.convertedToKing;
            m.nextMove = null;
            m.bitOriginalPosition = this.bitOriginalPosition;

            return m;
        }

        /// <summary>
        /// Returns the node type
        /// </summary>
        public override eGameType GetNodeType()
        {
            return eGameType.Move;
        }
    }

    /// <summary>
    /// Basic checkers settings for new game
    /// </summary>
    public class CheckersSettings
    {
        public int BoardSize
        { get; set; }

        public enumFigureColor FirstToMove
        { get; set; }

        public int NumFigureRows
        { get; set; }

        public bool WhiteMovesUp
        { get; set; }

        public bool IsBoardFlipped
        { get; set; }

        public bool PlayedOnWhiteFields
        { get; set; }

        public bool ContinueJumpAfterPromotion
        { get; set; }

        public List<enumCaptureConstraint> CaptureContraintList
        { get; set; }

        public CheckersSettings()
        {
            CaptureContraintList = new List<enumCaptureConstraint>();
        }
    }

    /// <summary>
    /// Game Item Type
    /// </summary>
    public enum eGameType { Move, Comment, NAG, GamePosition, EmptyMove, Variation, None };

    /// <summary>
    /// Game Item Abstract class
    /// </summary>
    public abstract class GameItem
    {
        /// <summary>
        /// Returns the node type
        /// </summary>
        public abstract eGameType GetNodeType();

    }

    /// <summary>
    /// Comment in the game
    /// </summary>
    public class GameComment: GameItem
    {
        public string Comment
        { get; set; }

        public GameComment(string comment)
        {
            Comment = comment;
        }

        /// <summary>
        /// Returns the node type - comment
        /// </summary>
        public override eGameType GetNodeType()
        {
            return eGameType.Comment;
        }
    }

    /// <summary>
    /// Game Position - Setup FEN String in the game notation
    /// </summary>
    public class GamePosition : GameItem
    {
        public BitBoard PackedPosition
        { get; set; }

        public GamePosition(BitBoard packedpos)
        {
            PackedPosition = packedpos;
        }

        public override eGameType GetNodeType()
        {
            return eGameType.GamePosition;
        }           
    }

    /// <summary>
    /// Class representing empty move in the game
    /// </summary>
    public class EmptyMove : GameItem
    {
        /// <summary>
        /// Returns the node type - empty move
        /// </summary>
        public override eGameType GetNodeType()
        {
            return eGameType.EmptyMove;
        }        
    }

    /// <summary>
    /// Game Strength represented by NAG
    /// </summary>
    public class GameNAG : GameItem
    {
        public int NAGNumber
        { get; set; }

        public GameNAG(int nag)
        {
            NAGNumber = nag;
        }

        /// <summary>
        /// Returns the node type
        /// </summary>
        public override eGameType GetNodeType()
        {
            return eGameType.NAG;
        }    
    }

    /// <summary>
    /// Variation wrapper class
    /// </summary>
    public class GameVariation : GameItem
    {
        public override eGameType GetNodeType()
        {
            return eGameType.Variation;
        }
    }

    /// <summary>
    /// Game Tree representation
    /// </summary>
    public class GameTree
    {
        /// <summary>
        /// Item of the game
        /// </summary>
        public GameItem GameItem
        { get; set; }

        public GameTree Parent
        { get; set; }

        /// <summary>
        /// Child nodes
        /// </summary>
        private List<GameTree> _childNodes;

        private int _Index = 0;

        public IEnumerable<GameTree> ChildNodes
        {
            get
            {
                return _childNodes;
            }
        }
       

        public GameTree(GameItem item, GameTree parent = null)
        {
            GameItem = item;
            Parent = parent;
            _childNodes = new List<GameTree>();
        }

        /// <summary>
        /// Returns the index in the tree
        /// </summary>
        public int Index
        {
            get
            {
                return _Index;
            }
        }


        /// <summary>
        /// Checkes the node without descendants
        /// </summary>
        public bool IsLeaf()
        {
            return _childNodes.Count == 0;
        }

        /// <summary>
        /// Checks the top-most node
        /// </summary>
        public bool IsRoot()
        {
            return Parent == null;
        }

        ///// <summary>
        ///// Adds child node - possibly obsolete
        ///// </summary>
        //public GameTree AddChild(GameItem item)
        //{
        //    GameTree treeItem = new GameTree(item, this);
        //    _childNodes.Add(treeItem);
        //    return treeItem;
        //}

        /// <summary>
        /// Adds the child node
        /// </summary>
        public void AddChild(GameTree treeItem)
        {
            treeItem.Parent = this;
            _childNodes.Add(treeItem);
            int childIndex = _childNodes.Count;
            treeItem._Index = Convert.ToInt32(this._Index.ToString() + childIndex.ToString());
        }

        /// <summary>
        /// Returns the child collection count
        /// </summary>
        public int GetChildCount()
        {
            return _childNodes.Count;
        }

        /// <summary>
        /// Clears all the child nodes
        /// </summary>
        public void ClearItems()
        {
            _childNodes.Clear();
        }

        /// <summary>
        /// Returns the child node by index
        /// </summary>
        public GameTree GetChildByIndex(int index)
        {
            return _childNodes[index];
        }

        /// <summary>
        /// Wrapper method for game type
        /// </summary>
        public eGameType GetNodeType()
        {
            if (GameItem == null)
            {
                return eGameType.None;
            }
            else
            {
                return GameItem.GetNodeType();
            }
        }

        /// <summary>
        /// Returns previous sibling node
        /// </summary>
        public GameTree GetPreviousSibling()
        {
            if (Parent == null)
            {
                return null;
            }
            else
            {
                int index = Parent._childNodes.IndexOf(this);
                if (index < 0)
                    throw new Exception("Child is not in parent collection");
                // if first child
                if (index == 0)
                {
                    return null;
                }
                else
                {
                    return Parent._childNodes[index - 1];
                }
            }
        }

       
        /// <summary>
        /// Returns the next sibling node
        /// </summary>
        public GameTree GetNextSibling()
        {
            if (Parent == null)
            {
                return null;
            }
            else
            {
                int index = Parent._childNodes.IndexOf(this);
                if (index < 0)
                    throw new Exception("Child is not in parent collection");
                // if first child
                if (index >= Parent.GetChildCount())
                {
                    return null;
                }
                else
                {
                    return Parent._childNodes[index + 1];
                }
            }
        }

        /// <summary>
        /// Returns the previous sibling node, if there is none, continues the search in higher tree level
        /// </summary>
        /// <returns></returns>
        public GameTree GetPreviousNodeUpToRoot()
        {
            // init variables
            GameTree item = this;

            while (item.IsRoot() == false)
            {
                // try to get previous item at this tree level
                GameTree prevItem = item.GetPreviousSibling();
                if (prevItem != null)
                    return prevItem;

                // if not, go higher
                item = item.Parent;
            }

            return null;
        }
    }      
}
